package com.zbs.framework.swagger.scan;

import com.zbs.framework.swagger.annotations.*;
import com.zbs.framework.swagger.constants.SwaggerApiConstant;
import com.zbs.framework.swagger.entity.Api;
import com.zbs.framework.swagger.entity.ApiEntityModel;
import com.zbs.framework.swagger.entity.RequestParams;
import com.zbs.framework.swagger.enums.MethodType;
import com.zbs.framework.swagger.enums.RequestType;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ApiBeanNameGenerator extends AnnotationBeanNameGenerator {


    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        try {
            String beanClassName = definition.getBeanClassName();
            Class<?> aClass = Class.forName(beanClassName);
            RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class);
            ApiIgnore apiIgnoreRequest = aClass.getAnnotation(ApiIgnore.class);
            if (apiIgnoreRequest != null) return super.generateBeanName(definition, registry);
            if (requestMapping != null) {
                ApiTags apiTags = aClass.getAnnotation(ApiTags.class);

                String apiName = aClass.getName().substring(aClass.getName().lastIndexOf('.') + 1);
                String group = "默认分组";
                if (apiTags != null && StringUtils.hasLength(apiTags.value())) {
                    apiName = apiTags.value();
                }

                if (apiTags != null && StringUtils.hasLength(apiTags.group())) {
                    group = apiTags.group();
                }

                List<Api> list = new ArrayList<>();

                Boolean enableRequestMapping = SwaggerApiConstant.enable.get(SwaggerApiConstant.enableRequestMapping);
                Method[] methods = aClass.getMethods();
                Arrays.stream(methods).forEach(method -> {
                    GetMapping getMapping = method.getAnnotation(GetMapping.class);
                    PutMapping putMapping = method.getAnnotation(PutMapping.class);
                    PostMapping postMapping = method.getAnnotation(PostMapping.class);
                    DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
                    RequestMapping rm = method.getAnnotation(RequestMapping.class);
                    ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
                    ApiIgnore apiIgnore = method.getAnnotation(ApiIgnore.class);
                    List<String> apiIgnoreFields = new ArrayList<>();
                    apiIgnoreFields.add("serialVersionUID");
                    if (getMapping == null && putMapping == null && postMapping == null && deleteMapping == null && rm == null) {
                        return;
                    }
                    Api api = Api.builder().apiName(method.getName()).requestType(RequestType.FORM_DATA.name()).build();
                    if (apiOperation != null) {
                        api.setApiName(apiOperation.value()).setRequestType(apiOperation.requestType().name());
                    }
                    if (getMapping != null) {
                        api.setMethod(MethodType.GET.name()).setSort(1).setPath(getMapping.value());
                    }
                    if (postMapping != null) {
                        api.setMethod(MethodType.POST.name()).setSort(2).setPath(postMapping.value());
                    }

                    if (rm != null) {
                        if (enableRequestMapping != null && enableRequestMapping) {
                            return;
                        }
                        api.setMethod(MethodType.POST.name()).setSort(2).setPath(rm.value());
                    }

                    if (putMapping != null) {
                        api.setMethod(MethodType.PUT.name()).setSort(3).setPath(putMapping.value());
                    }
                    if (deleteMapping != null) {
                        api.setMethod(MethodType.DELETE.name()).setSort(4).setPath(deleteMapping.value());
                    }

                    if (apiIgnore != null) {
                        apiIgnoreFields =  Arrays.stream(apiIgnore.value()).distinct().collect(Collectors.toList());
                    }

                    // 接收参数
                    List<RequestParams> requestParamsList = new ArrayList<>();
                    final List<String> finalApiIgnoreFields = apiIgnoreFields;
                    Arrays.stream(method.getParameters()).forEach(p -> {
                        try {
                            List<RequestParams> declaredFieldsInfo = this.getDeclaredFieldsInfo(p.getType().getName(),finalApiIgnoreFields,true);
                            if (CollectionUtils.isEmpty(declaredFieldsInfo)) {
                                if (finalApiIgnoreFields.contains(p.getName())
                                        || p.getType().equals(HttpServletRequest.class)
                                        || p.getType().equals(HttpServletResponse.class)) {
                                    return;
                                }
                                ApiParam apiParam = p.getAnnotation(ApiParam.class);
                                RequestParam requestParam = p.getAnnotation(RequestParam.class);
                                RequestParams requestParams = new RequestParams();
                                requestParams.setName(p.getName())
                                        .setDescribe(p.getName())
                                        .setRequired(false).setArray(false)
                                        .setDataType(p.getType().getName());
                                if (apiParam != null) {
                                    requestParams.setRequired(apiParam.required())
                                            .setDescribe(apiParam.value());
                                }
                                if (requestParam != null) {
                                    requestParams.setRequired(requestParam.required());
                                }

                                String array = this.isArray(p.getType().getName());
                                String listGenericType = this.getListGenericType(p.getParameterizedType());
                                String type = "";
                                if (StringUtils.hasLength(array)) {
                                    type = array;
                                }
                                if (StringUtils.hasLength(listGenericType)) {
                                    type = listGenericType;
                                }
                                if (StringUtils.hasLength(type))  {
                                    requestParams.setArray(true);
                                    requestParams.setDataType(type);
                                    this.recursive(type, requestParams, finalApiIgnoreFields,true);
                                }
                                requestParamsList.add(requestParams);
                            } else {
                                requestParamsList.addAll(declaredFieldsInfo);
                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    });
                    // 设置接收参数
                    api.setRequestParamsList(requestParamsList);

                    //设置返回参数
                    if (apiOperation != null) {
                        Class<?> response = apiOperation.response();
                        try {
                            List<RequestParams> declaredFieldsInfo = this.getDeclaredFieldsInfo(response.getName(), finalApiIgnoreFields, false);
                            if (CollectionUtils.isEmpty(declaredFieldsInfo)) {
                                RequestParams requestParams = new RequestParams();
                                String typeName = response.getTypeName();
                                requestParams.setName("").setDescribe("").setArray(apiOperation.responseIsArray()).setDataType(typeName);
                                declaredFieldsInfo.add(requestParams);
                            }
                            api.setResponseParamsList(declaredFieldsInfo);
                            api.setResponseIsArray(apiOperation.responseIsArray());
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                    list.add(api);
                });
                ApiEntityModel apiModel = ApiEntityModel.builder().group(group).apiName(apiName).apiList(list).path(requestMapping.value()).build();
                SwaggerApiConstant.apiModelList.add(apiModel);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //走父类的方法
        return super.generateBeanName(definition, registry);
    }

    /**
     * 获取累中的所有属性名称及注解内容
     *
     * @return
     */
    private List<RequestParams> getDeclaredFieldsInfo(String className, List<String> finalApiIgnoreFields, boolean flag) throws ClassNotFoundException {
        Class<?> clazz = Class.forName(className);
        List<RequestParams> requestParamsList = new ArrayList<>();
        String name = "";
        if (clazz.getSuperclass() != null) {
            name =  clazz.getSuperclass().getName();
            Class<?> superclass = Class.forName(name);
            this.getField(superclass,finalApiIgnoreFields,flag,requestParamsList);
        }
       this.getField(clazz,finalApiIgnoreFields,flag,requestParamsList);
        return requestParamsList;
    }

    private void getField(Class<?> clazz, List<String> finalApiIgnoreFields, boolean flag, List<RequestParams> requestParamsList) {
        ApiModel apiModel = clazz.getAnnotation(ApiModel.class);
        if (apiModel != null) {
            Arrays.stream(clazz.getDeclaredFields()).forEach(field -> {
                field.setAccessible(true);
                if (flag && finalApiIgnoreFields.contains(field.getName()) || field.getName().equals("serialVersionUID"))return;
                ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
                RequestParams requestParams = new RequestParams();
                String typeName = field.getType().getName();
                requestParams.setName(field.getName()).setDescribe(field.getName()).setRequired(false).setArray(false).setDataType(typeName);
                if (apiModelProperty != null) {
                    requestParams.setDescribe(apiModelProperty.value())
                            .setRequired(apiModelProperty.required());
                }
                String array = this.isArray(field.getType().getName());
                String listGenericType = this.getListGenericType(field.getGenericType());
                String type = "";
                if (StringUtils.hasLength(array)) {
                    type = array;
                }
                if (StringUtils.hasLength(listGenericType)) {
                    type = listGenericType;
                }
                if (StringUtils.hasLength(type))  {
                    requestParams.setArray(true);
                    requestParams.setDataType(type);
                    try {
                        this.recursive(type, requestParams, finalApiIgnoreFields,flag);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                } else {
                    try {
                        String tGenericType = this.getTGenericType(field.getType().getName());
                        if (StringUtils.hasLength(tGenericType)) {
                            this.recursive(tGenericType, requestParams, finalApiIgnoreFields,flag);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }


                requestParamsList.add(requestParams);
            });
        }
    }

    private void recursive(String className, RequestParams params, List<String> finalApiIgnoreFields, boolean flag) throws ClassNotFoundException {
        List<RequestParams> requestParamsList = new ArrayList<>();
        Class<?> clazz = Class.forName(className);
        String name = "";
        if (clazz.getSuperclass() != null) {
            name =  clazz.getSuperclass().getName();
            Class<?> superclass = Class.forName(name);
            this.getChildFiled(superclass,requestParamsList,finalApiIgnoreFields,flag);
        }
        this.getChildFiled(clazz,requestParamsList,finalApiIgnoreFields,flag);
        params.setChildren(requestParamsList);
    }


    private void getChildFiled(Class<?> clazz ,List<RequestParams> requestParamsList,List<String> finalApiIgnoreFields, boolean flag) {
        ApiModel apiModel = clazz.getAnnotation(ApiModel.class);
        if (apiModel != null) {
            Arrays.stream(clazz.getDeclaredFields()).forEach(field -> {
                field.setAccessible(true);
                if (flag && finalApiIgnoreFields.contains(field.getName())) return;
                RequestParams requestParams = new RequestParams();
                ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
                requestParams.setName(field.getName()).setDescribe(field.getName()).setRequired(false).setArray(false).setDataType(field.getType().getName());
                if (apiModelProperty != null) {
                    requestParams.setDescribe(apiModelProperty.value())
                            .setRequired(apiModelProperty.required());
                }
                String array = this.isArray(field.getType().getName());
                String listGenericType = this.getListGenericType(field.getGenericType());
                String type = "";
                // 判断是否是一维数组
                if (StringUtils.hasLength(array)) {
                    type = array;
                }
                // 判断是否是集合list
                if (StringUtils.hasLength(listGenericType)) {
                    type = listGenericType;
                }
                if (StringUtils.hasLength(type))  {
                    requestParams.setArray(true);
                    requestParams.setDataType(type);
                    try {
                        if (!type.equals(clazz.getName())){
                            this.recursive(type, requestParams, finalApiIgnoreFields,flag);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                } else {
                    try {
                        String tGenericType = this.getTGenericType(field.getType().getName());
                        if (StringUtils.hasLength(tGenericType)) {
                            if (!tGenericType.equals(clazz.getName())){
                                this.recursive(tGenericType, requestParams, finalApiIgnoreFields,flag);
                            }
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                requestParamsList.add(requestParams);
            });
        }
    }

    /**
     * 判断数组中的类型
     *
     * @param genericType
     * @return
     */
    private String getListGenericType(Type genericType){
        if (null != genericType) {
            try {
                if (genericType instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType) genericType;
                    // 得到泛型里的class类型对象
                    Class<?> actualTypeArgument = (Class<?>) pt.getActualTypeArguments()[0];
                    return actualTypeArgument.getName();
                }
            } catch (Exception e) {
                return "";
            }
        }
        return "";
    }


    private String isArray(String genericType) {
        Class<?> clazz = null;
        try {
            // 完整类名带有 . 的是对象类型，如果没有则是基本数据类型 java.lang.String   int string long...
            if (genericType.indexOf('.') != -1){
                clazz = Class.forName(genericType);
                if (clazz.isArray()) {
                    return clazz.getComponentType().getName();
                }
            }
        } catch (ClassNotFoundException e) {
            return "";
        }
        return "";
    }

    private String getTGenericType(String genericType){
        Class<?> clazz = null;
        try {
            // 完整类名带有 . 的是对象类型，如果没有则是基本数据类型 java.lang.String   int string long...
            if (genericType.indexOf('.') != -1) {
                clazz = Class.forName(genericType);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if (clazz != null) {
            ApiModel apiModel = clazz.getAnnotation(ApiModel.class);
            if(apiModel != null) {
                return clazz.getName();
            }
        }
        return "";
    }

}